package com.junxonline.instant.api.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.junxonline.instant.common.util.common.RedisUtils;
import com.junxonline.instant.dao.entity.model.log.LogApi;
import com.junxonline.instant.common.annotation.ApiLog;
import com.junxonline.instant.common.constant.CommonConstant;
import com.junxonline.instant.common.constant.RedisMqChannelConstant;
import com.junxonline.instant.common.entity.common.CommonResult;
import com.junxonline.instant.util.NetworkUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 接口调用日志切面类
 *
 * @author JunX
 * @date 2022-05-11
 */
@Log4j2
@Aspect
@Component
public class ApiLogAspect {

    @Autowired
    private RedisUtils redisUtils;

    private ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 定义切入点为标识了注解@ApiLog的方法
     */
    @Pointcut(value = "@annotation(api)")
    public void apiLog(ApiLog api) {

    }

    /**
     * 前置通知
     *
     * @param joinPoint 切入点
     * @param api       接口调用日志注解
     */
    @Before(value = "apiLog(api)")
    public void before(JoinPoint joinPoint, ApiLog api) {
        this.startTime.set(System.currentTimeMillis());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("token");
        String terminalCode = request.getHeader("terminalCode");
        String os = request.getHeader("User-Agent");
        log.info("************************ 收到请求 ************************");
        log.info("终端编号：" + terminalCode);
        log.info("终端Token：" + token);
        log.info("终端OS：" + os);
        log.info("请求地址：" + request.getRequestURI());
        log.info("请求类型：" + request.getMethod());
        log.info("接口名称：" + api.label());
        log.info("接口类型：" + api.type().getLabel());
        log.info("接口版本：" + api.version().getLabel());
        log.info("请求主机IP：" + NetworkUtils.getIpAddress(request));
        log.info("请求方法路径：" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("请求参数：" + JSON.toJSONString(joinPoint.getArgs()));
        log.info("*********************************************************");
    }

    /**
     * 后置通知
     *
     * @param joinPoint 切入点
     * @param api       接口调用注解
     * @param result    公共响应对象
     */
    @AfterReturning(value = "apiLog(api)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, ApiLog api, CommonResult result) {
        this.saveLog(joinPoint, api, CommonConstant.SUCCESS);
        Long endTime = System.currentTimeMillis();
        log.info("************************ 返回数据 ************************");
        log.info("返回响应码：" + result.getCode());
        log.info("返回响应信息：" + result.getMsg());
        log.info("返回数据：" + JSON.toJSONString(result.getData()));
        log.info("请求耗时：" + (endTime - this.startTime.get()) + "ms");
        log.info("*********************************************************");
    }

    /**
     * 异常通知
     *
     * @param joinPoint 切入点
     * @param api       接口调用注解
     * @param e         异常
     */
    @AfterThrowing(value = "apiLog(api)", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, ApiLog api, Exception e) {
        this.saveLog(joinPoint, api, CommonConstant.FAIL);
        log.error("************************ 抛出异常 ************************");
        log.error("异常名称：" + e.getClass());
        log.error("异常信息：" + e.getMessage());
        log.error("*********************************************************");
    }

    /**
     * 发送接口调用日志消息
     *
     * @param joinPoint 切入点
     * @param api       接口调用注解
     * @param status    请求结果
     */
    private void saveLog(JoinPoint joinPoint, ApiLog api, Integer status) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("token");
        String terminalCode = request.getHeader("terminalCode");
        if (!StrUtil.isBlank(token) && !StrUtil.isBlank(terminalCode)) {
            // Redis消息队列发送接口调用日志消息
            LogApi logApi = new LogApi();
            logApi.setName(api.label());
            logApi.setType(api.type().getValue());
            logApi.setVersion(api.version().getLabel());
            logApi.setUrl(request.getRequestURI());
            logApi.setToken(token);
            logApi.setTerminalCode(terminalCode);
            logApi.setTerminalIp(NetworkUtils.getIpAddress(request));
            logApi.setTargetIp(request.getHeader("Host"));
            logApi.setOs(request.getHeader("User-Agent"));
            logApi.setStatus(status);
            logApi.setRequestTime(DateUtil.date());
            logApi.setParams(JSON.toJSONString(joinPoint.getArgs()));
            redisUtils.convertAndSend(RedisMqChannelConstant.KEY_REDIS_MSG_CHANNEL_LOG_API, JSON.toJSONString(logApi));
        }
    }

}
